#include "stdafx.h"
#include "stdwin.h"
#include "app.h"
#include "gfx.h"

#pragma region STATIC_VARIABLES

static ID3D11Device* lpDevice;
static ID3D11DeviceContext* lpDeviceContext;
static IDXGISwapChain* lpSwapChain;
static ID3D11RenderTargetView* lpRenderTargetView;
static ID3D11Texture2D* lpDepthTexture;
static ID3D11DepthStencilView* lpDepthStencil;

#pragma endregion

BOOL InitD3DFailed(LPCTSTR message)
{
 FreeD3D();
 MessageBox(GetMainWindow(), message, TEXT("Error"), MB_ICONSTOP);
 return FALSE;
}

BOOL InitD3D(void)
{
 // window must be valid
 if(!GetMainWindow()) return FALSE;
 if(!IsWindow(GetMainWindow())) return FALSE;

 // initialize swap chain descriptor
 DXGI_SWAP_CHAIN_DESC scd;
 ZeroMemory(&scd, sizeof(scd));
 scd.BufferCount = 1;
 scd.BufferDesc.Width = 0;
 scd.BufferDesc.Height = 0;
 scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
 scd.BufferDesc.RefreshRate.Numerator = 60;
 scd.BufferDesc.RefreshRate.Denominator = 1;
 scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
 scd.OutputWindow = GetMainWindow();
 scd.SampleDesc.Count = 1;
 scd.SampleDesc.Quality = 0;
 scd.Windowed = TRUE;

 // create device and swap chain
 D3D_FEATURE_LEVEL featureLevelReq[2] = { D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0 };
 D3D_FEATURE_LEVEL featureLevel;
 HRESULT result = D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, D3D11_CREATE_DEVICE_DEBUG, &featureLevelReq[0], 2, D3D11_SDK_VERSION, &scd, &lpSwapChain, &lpDevice, &featureLevel, &lpDeviceContext);
 if(FAILED(result)) return InitD3DFailed(TEXT("Failed to create D3D11 device and swap chain."));

 // feature level must be Direct3D 11 or 11.1
 if(!(featureLevel == D3D_FEATURE_LEVEL_11_0 || featureLevel == D3D_FEATURE_LEVEL_11_1))
    return InitD3DFailed(TEXT("Direct3D 11 is either not installed or not supported on this computer."));

 // create render target view
 ID3D11Texture2D* lpBackBuffer = NULL;
 result = lpSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&lpBackBuffer);
 if(FAILED(result)) {
    FreeD3D();
    MessageBox(GetMainWindow(), TEXT("Failed to obtain handle to back buffer."), TEXT("Error"), MB_ICONSTOP);
    return FALSE;
   }
 result = lpDevice->CreateRenderTargetView(lpBackBuffer, NULL, &lpRenderTargetView);
 if(FAILED(result)) {
    FreeD3D();
    MessageBox(GetMainWindow(), TEXT("Failed to create render target view."), TEXT("Error"), MB_ICONSTOP);
    return FALSE;
   }

 // get backbuffer descriptor
 D3D11_TEXTURE2D_DESC bbd = {0};
 lpBackBuffer->GetDesc(&bbd);

 // backbuffer no longer needed
 lpBackBuffer->Release();
 lpBackBuffer = 0;

 // fill depth buffer parameters
 D3D11_TEXTURE2D_DESC dsd;
 ZeroMemory(&dsd, sizeof(dsd));
 dsd.Width = bbd.Width;
 dsd.Height = bbd.Height;
 dsd.MipLevels = 1;
 dsd.ArraySize = 1;
 dsd.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
 dsd.SampleDesc.Count = 1;
 dsd.SampleDesc.Quality = 0;
 dsd.Usage = D3D11_USAGE_DEFAULT;
 dsd.BindFlags = D3D11_BIND_DEPTH_STENCIL;

 // create depth buffer
 if(FAILED(lpDevice->CreateTexture2D(&dsd, NULL, &lpDepthTexture))) {
    FreeD3D();
    return ErrorBox(GetMainWindow(), TEXT("Failed to create depth texture."));
   }

// create depth stencil view
 D3D11_DEPTH_STENCIL_VIEW_DESC dsvd;
 ZeroMemory(&dsvd, sizeof(dsvd));
 dsvd.Format = dsd.Format;
 dsvd.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
 if(FAILED(lpDevice->CreateDepthStencilView(lpDepthTexture, &dsvd, &lpDepthStencil))) {
    FreeD3D();
    return ErrorBox(GetMainWindow(), TEXT("Failed to create depth stencil view."));
   }

 // set render target
 lpDeviceContext->OMSetRenderTargets(1, &lpRenderTargetView, lpDepthStencil);

 // get window dimensions
 RECT rect;
 GetClientRect(GetMainWindow(), &rect);
 LONG cx = rect.right - rect.left + 1;
 LONG cy = rect.bottom - rect.top + 1;
 if(cx < 1) cx = 1;
 if(cy < 1) cy = 1;

 // initialize viewport
 D3D11_VIEWPORT vp;
 vp.Width = (FLOAT)cx;
 vp.Height = (FLOAT)cy;
 vp.MinDepth = 0.0f;
 vp.MaxDepth = 1.0f;
 vp.TopLeftX = 0;
 vp.TopLeftY = 0;
 lpDeviceContext->RSSetViewports(1, &vp);

 // // load vertex shaders
 // if(!InitVertexShaders()) {
 //    FreeD3D();
 //    MessageBox(GetMainWindow(), TEXT("Failed to load vertex shaders."), TEXT("Error"), MB_ICONSTOP);
 //    return FALSE;
 //   }
 // 
 // // load pixel shaders
 // if(!InitPixelShaders()) {
 //    FreeD3D();
 //    MessageBox(GetMainWindow(), TEXT("Failed to load pixel shaders."), TEXT("Error"), MB_ICONSTOP);
 //    return FALSE;
 //   }
 // 
 // // load input layouts
 // if(!InitInputLayouts()) {
 //    FreeD3D();
 //    return FALSE;
 //   }
 // 
 // // load rasterization states
 // if(!InitRasterizerStates()) {
 //    FreeD3D();
 //    return FALSE;
 //   }
 // 
 // // load samplers
 // if(!InitSamplers()) {
 //    FreeD3D();
 //    return FALSE;
 //   }
 // 
 // // create constant buffer
 // result = CreateConstantMatrixBuffer(&lpPMatrix);
 // if(FAILED(result))  {
 //    FreeD3D();
 //    MessageBox(GetMainWindow(), TEXT("Failed to create projection matrix buffer."), TEXT("Error"), MB_ICONSTOP);
 //    return FALSE;
 //   }
 // 
 // // initialize grid
 // if(!InitGrid()) {
 //    FreeD3D();
 //    MessageBox(GetMainWindow(), TEXT("Failed to initialize grid."), TEXT("Error"), MB_ICONSTOP);
 //    return FALSE;
 //   }
 // 
 // // TEMPORARY BEGIN
 // InitCamera();
 // // TEMPORARY END

 return TRUE;
}

BOOL FreeD3D(void)
{
 // clear D3D context
 if(lpDeviceContext) lpDeviceContext->ClearState();

 // // delete grid
 // FreeGrid();
 // 
 // // delete matrix buffers
 // if(lpPMatrix) lpPMatrix->Release();
 // lpPMatrix = NULL;
 // 
 // // delete samplers
 // FreeSamplers();
 // 
 // // delete rasterization states
 // FreeRasterizerStates();
 // 
 // // delete input layouts
 // FreeInputLayouts();
 // 
 // // delete shaders
 // FreeVertexShaders();
 // FreePixelShaders();

 // delete D3D context
 if(lpDepthTexture) {
    lpDepthTexture->Release();
    lpDepthTexture = NULL;
   }
 if(lpDepthStencil) {
    lpDepthStencil->Release();
    lpDepthStencil = NULL;
   }
 if(lpRenderTargetView) {
    lpRenderTargetView->Release();
    lpRenderTargetView = NULL;
   }
 if(lpSwapChain) {
    lpSwapChain->Release();
    lpSwapChain = NULL;
   }
 if(lpDeviceContext) {
    lpDeviceContext->Release();
    lpDeviceContext = NULL;
   }
 if(lpDevice) {
    lpDevice->Release();
    lpDevice = NULL;
   }

 return TRUE;
}

BOOL ResizeD3D(UINT dx, UINT dy)
{
 // must have the following
 if(!lpDeviceContext) return FALSE;
 if(!lpSwapChain) return FALSE;
 if(!lpRenderTargetView) return FALSE;

 // stop rendering
 lpDeviceContext->OMSetRenderTargets(0, NULL, NULL);

 // release depth texture
 if(lpDepthTexture) {
    lpDepthTexture->Release();
    lpDepthTexture = NULL;
   }

 // release depth stencil view
 if(lpDepthStencil) {
    lpDepthStencil->Release();
    lpDepthStencil = NULL;
   }

 // release render target view
 if(lpRenderTargetView) {
    lpRenderTargetView->Release();
    lpRenderTargetView = NULL;
   }

 // resize display buffers
 HRESULT result = lpSwapChain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN, 0);
 if(FAILED(result)) return FALSE;

 // get interface to back buffer
 ID3D11Texture2D* lpBackBuffer = NULL;
 result = lpSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&lpBackBuffer);
 if(FAILED(result)) return FALSE;

 // create render target view
 result = lpDevice->CreateRenderTargetView(lpBackBuffer, NULL, &lpRenderTargetView);
 if(FAILED(result)) return FALSE;

 // get backbuffer descriptor
 D3D11_TEXTURE2D_DESC bbd = {0};
 lpBackBuffer->GetDesc(&bbd);

 // back buffer no longer needed
 lpBackBuffer->Release();
 lpBackBuffer = NULL;

 // fill depth buffer parameters
 D3D11_TEXTURE2D_DESC dsd;
 ZeroMemory(&dsd, sizeof(dsd));
 dsd.Width = bbd.Width;
 dsd.Height = bbd.Height;
 dsd.MipLevels = 1;
 dsd.ArraySize = 1;
 dsd.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
 dsd.SampleDesc.Count = 1;
 dsd.SampleDesc.Quality = 0;
 dsd.Usage = D3D11_USAGE_DEFAULT;
 dsd.BindFlags = D3D11_BIND_DEPTH_STENCIL;

 // create depth buffer
 if(FAILED(lpDevice->CreateTexture2D(&dsd, nullptr, &lpDepthTexture))) {
    FreeD3D();
    return ErrorBox(GetMainWindow(), TEXT("Failed to create depth texture."));
   }

// create depth stencil view
 D3D11_DEPTH_STENCIL_VIEW_DESC dsvd;
 ZeroMemory(&dsvd, sizeof(dsvd));
 dsvd.Format = dsd.Format;
 dsvd.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
 if(FAILED(lpDevice->CreateDepthStencilView(lpDepthTexture, &dsvd, &lpDepthStencil))) {
    FreeD3D();
    return ErrorBox(GetMainWindow(), TEXT("Failed to create depth stencil view."));
   }

 // set render target
 lpDeviceContext->OMSetRenderTargets(1, &lpRenderTargetView, lpDepthStencil);

 // set viewport
 SetViewport(dx, dy);

 return TRUE;
}

BOOL RenderFrame(void)
{
 // validate
 if(!lpDeviceContext) return FALSE;
 if(!lpSwapChain) return FALSE;

 // clear buffers
 FLOAT color[4] = { 0.0f, 0.125f, 0.3f, 1.0f };
 lpDeviceContext->ClearRenderTargetView(lpRenderTargetView, color);
 lpDeviceContext->ClearDepthStencilView(lpDepthStencil, D3D11_CLEAR_DEPTH, 1.0f, 0);

 // // retrieve camera data
 // const real32* _PV = camera.GetProjectionViewMatrix();
 // XMMATRIX P = XMMatrixIdentity();
 // P(0,0) = _PV[0x0];
 // P(0,1) = _PV[0x1];
 // P(0,2) = _PV[0x2];
 // P(0,3) = _PV[0x3];
 // P(1,0) = _PV[0x4];
 // P(1,1) = _PV[0x5];
 // P(1,2) = _PV[0x6];
 // P(1,3) = _PV[0x7];
 // P(2,0) = _PV[0x8];
 // P(2,1) = _PV[0x9];
 // P(2,2) = _PV[0xA];
 // P(2,3) = _PV[0xB];
 // P(3,0) = _PV[0xC];
 // P(3,1) = _PV[0xD];
 // P(3,2) = _PV[0xE];
 // P(3,3) = _PV[0xF];
 // 
 // // update constant buffers
 // lpDeviceContext->UpdateSubresource(lpPMatrix, 0, NULL, &P, 0, 0);
 // lpDeviceContext->VSSetConstantBuffers(0, 1, &lpPMatrix);
 // 
 // // render grid
 // RenderGrid();
 // 
 // // render models
 // RenderScene();

 // present
 lpSwapChain->Present(0, 0);

 return TRUE;
}

BOOL SetViewport(UINT dx, UINT dy)
{
 if(!lpDeviceContext) return FALSE;
 D3D11_VIEWPORT vp = { 0.0f, 0.0f, (FLOAT)dx, (FLOAT)dy, 0.0f, 1.0f };
 lpDeviceContext->RSSetViewports(1, &vp);
 return TRUE;
}

ID3D11Device* GetD3DDevice(void)
{
 return lpDevice;
}

ID3D11DeviceContext* GetD3DDeviceContext(void)
{
 return lpDeviceContext;
}